//
//  CCVideoDecoder.m
//  IntellDev_new
//
//  Created by os on 2021/3/12.
//

#import "CCVideoDecoder.h"
#import <VideoToolBox/VideoToolbox.h>

@interface CCVideoDecoder ()

@property (nonatomic, strong) dispatch_queue_t decodeQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

// 解码会话
@property (nonatomic) VTDecompressionSessionRef decodeSession;

@end

@implementation CCVideoDecoder {
    NSUInteger  _spsSize;
    NSUInteger  _ppsSize;
    CMVideoFormatDescriptionRef _decodeDesc;
}

- (void)dealloc {
    [self removeH264HwDecoder];
}

- (void)removeH264HwDecoder {
    if (_decodeSession) {
        VTDecompressionSessionInvalidate(_decodeSession);
        CFRelease(_decodeSession);
        _decodeSession = NULL;
    }
    if (_decodeDesc) {
        CFRelease(_decodeDesc);
        _decodeDesc = NULL;
    }
}

/// 解码回调函数
void videoDecompressionOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
                                      void * CM_NULLABLE sourceFrameRefcon,
                                      OSStatus status,
                                      VTDecodeInfoFlags infoFlags,
                                      CM_NULLABLE CVImageBufferRef imageBuffer,
                                      CMTime presentationTimeStamp,
                                      CMTime presentationDuration) {
    
    if (status != noErr) {
        NSLog(@"Video hard decode callback error status=%d", (int)status);
        return;
    }
    
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefcon;
    *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
    
    CCVideoDecoder *decoder = (__bridge CCVideoDecoder *)(decompressionOutputRefCon);
    
    dispatch_async(decoder.callbackQueue, ^{
       
        if (decoder.videoDelegate && [decoder.videoDelegate respondsToSelector:@selector(videoDecodedCallBack:)]) {
            [decoder.videoDelegate videoDecodedCallBack:imageBuffer];
        }
        
        CVPixelBufferRelease(imageBuffer);
        
    });
    
}

- (instancetype)initWithConfig:(CCVideoConfig *)config {
    if (self = [super init]) {
        self.config = config;
        // 创建解码队列与回调队列
        _decodeQueue = dispatch_queue_create("h264 hard decode queue", DISPATCH_QUEUE_SERIAL);
        _callbackQueue = dispatch_queue_create("h264 hard decode callback queue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)decodeH264Frames:(uint8_t *)inputBuffer withLength:(int)length {
    dispatch_async(_decodeQueue, ^{
        
//        for (int i = 0; i < length; i++) {
//            printf(" %x ", inputBuffer[i]);
//        }
//
//        printf("\n");
        
        uint8_t *data = NULL;
        uint8_t *pps = NULL;
        uint8_t *sps = NULL;

        long blockLength = 0;
        
        NaluUnit naluUnit;
        
        m_h264Buf = (unsigned char *)inputBuffer;
        m_h264BufSize = length;
        m_nCurPos = 0;

        while(ReadOneNaluFromBuf(&naluUnit)) {
            
            if(naluUnit.type == H264NT_SPS) { //sps
                
                _spsSize = naluUnit.size;
                sps = malloc(naluUnit.size);
                memcpy(sps, naluUnit.data, naluUnit.size);
                
            } else if(naluUnit.type == H264NT_PPS) { //pps
                
                _ppsSize = naluUnit.size;
                pps = malloc(naluUnit.size);
                memcpy(pps, naluUnit.data, naluUnit.size);
                
                const uint8_t * const parameterSetPointers[2] = {sps, pps};
                const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
                int naluHeaderLen = 4;
                
                OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);
                if (status != noErr) {
                    NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status = %d", (int)status);
                    return;
                }
                
                NSDictionary *destinationPixBufferAttrs =
                @{
                  (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //iOS上 nv12(uvuv排布) 而不是nv21（vuvu排布）
                  (id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
                  (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
                  (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
                  };
                
                VTDecompressionOutputCallbackRecord callbackRecord;
                callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
                callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
                
                status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decodeSession);
                                
                if (status != noErr) {
                    NSLog(@"Video hard DecodeSession create failed status = %d", (int)status);
                    return;
                }
                
                // 设置解码会话属性(实时编码)
                status = VTSessionSetProperty(_decodeSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
                NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", (int)status);
                
                if (sps) {
                    free(sps);
                    sps = NULL;
                }
                if (pps) {
                    free(pps);
                    pps = NULL;
                }
            }
            
            if (naluUnit.type == H264NT_SLICE || naluUnit.type == H264NT_SLICE_IDR) {
                
                blockLength = naluUnit.size+4;
                data =  (unsigned char *)malloc(blockLength);
                
//                int i = 0;
//                data[i++] = 0x00;
//                data[i++] = 0x00;
//                data[i++] = 0x00;
//                data[i++] = 0x01;
                
//                uint32_t naluSize = naluUnit.size;
//                uint8_t *pNaluSize = (uint8_t *)(&naluSize);
//                data[0] = *(pNaluSize + 3);
//                data[1] = *(pNaluSize + 2);
//                data[2] = *(pNaluSize + 1);
//                data[3] = *(pNaluSize);
                
                uint32_t dataLength32 = htonl(blockLength);
                memcpy(data, &dataLength32, sizeof(uint32_t));
                
                memcpy(data+4, &naluUnit.data[0], naluUnit.size);
                
                CVPixelBufferRef outputPixelBuffer = NULL;
                CMBlockBufferRef blockBuffer = NULL;
                CMBlockBufferFlags flag0 = 0;
                
                OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, data, blockLength, kCFAllocatorNull, NULL, 0, blockLength, flag0, &blockBuffer);
                
                if (status != kCMBlockBufferNoErr) {
                    NSLog(@"Video hard decode create blockBuffer error code = %d", (int)status);
                    return;
                }
                
                CMSampleBufferRef sampleBuffer = NULL;
                const size_t sampleSizeArray[] = {blockLength};
                
                status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
                
                if (status != noErr || !sampleBuffer) {
                    NSLog(@"Video hard decode create sampleBuffer failed status = %d", (int)status);
                    CFRelease(blockBuffer);
                    return;
                }
                
                VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
                VTDecodeInfoFlags infoFlag = kVTDecodeInfo_Asynchronous;

                status = VTDecompressionSessionDecodeFrame(_decodeSession, sampleBuffer, flag1, &outputPixelBuffer, &infoFlag);
                if (status == kVTInvalidSessionErr) {
                    NSLog(@"Video hard decode  InvalidSessionErr status = %d", (int)status);
                    [self removeH264HwDecoder];
                } else if (status == kVTVideoDecoderBadDataErr) {
                    NSLog(@"Video hard decode  BadData status = %d", (int)status);
                } else if (status == kVTVideoDecoderMalfunctionErr) {
                    NSLog(@"Decode failed status: kVTVideoDecoderMalfunctionErr");
                    CVPixelBufferRelease(outputPixelBuffer);
                    outputPixelBuffer = NULL;
                }else if (status != noErr) {
                    NSLog(@"Video hard decode failed status = %d", (int)status);
                }
                
                CFRelease(sampleBuffer);
                CFRelease(blockBuffer);
                
                if (data) {
                    free(data);
                    data = NULL;
                }
                
            }
            
            
        }
        
    });
}

unsigned char* m_h264Buf;
unsigned int  m_h264BufSize;
unsigned int  m_nCurPos;

bool ReadOneNaluFromBuf(NaluUnit *nalu)
{
    int i = m_nCurPos;
    while( i < m_h264BufSize  )
    {
        if(m_h264Buf[i++] == 0x00 && m_h264Buf[i++] == 0x00)
        {
            unsigned char c = m_h264Buf[i++];
            if((c == 0x1) || ((c == 0) && ( m_h264Buf[i++] == 0x01)) )
            {
                int pos = i;
                int num = 4;
                while (pos < m_h264BufSize)
                {
                    if(m_h264Buf[pos++] == 0x00 &&m_h264Buf[pos++] == 0x00)
                    {
                        c = m_h264Buf[pos++];
                        if(c == 0x1)
                        {
                            num = 3;
                            break;
                        }
                        else if( (c == 0) && ( m_h264Buf[pos++] == 0x01) )
                        {
                            num = 4;
                            break;
                        }
                    }
                }
                if(pos == m_h264BufSize)
                {
                    nalu->size = pos-i;
                }
                else
                {
                    nalu->size = (pos-num)-i;
                }
                nalu->type = m_h264Buf[i]&0x1f;
                nalu->data = &m_h264Buf[i];
                m_nCurPos = pos - num;
                return true;
            }
        }
    }
    return false;
}


@end
